package cytargetlinker.conversion;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import org.bridgedb.DataSource;
import org.bridgedb.IDMapperException;
import org.bridgedb.IDMapperStack;
import org.bridgedb.Xref;
import org.bridgedb.bio.BioDataSource;

import cytargetlinker.conversion.ArgsParser.AFilesIn;
import cytargetlinker.conversion.ArgsParser.AFilesOut;
import cytargetlinker.conversion.ArgsParser.AHelp;
import cytargetlinker.conversion.ArgsParser.GraphBuilder;
import cytargetlinker.conversion.graph.Graph;
import cytargetlinker.conversion.graph.Graph.Edge;
import cytargetlinker.conversion.graph.Graph.Node;


/**
 * Converts miRNA target text files from MiRecords to a XGMML or GML network.
 * 
 * The input files for this script can be downloaded here:
 * 
 * @author Thomas, Pooja, Naime, Tina
 */
public class MiRecords {
	
	private final static Logger log = Logger.getLogger(MiRecords.class.getName());
	
	public static void main(String argv[]) throws Exception {
		
		Args pargs = ArgsParser.parse(argv, Args.class);
		new MiRecordsConverter(pargs);
		
	}
	
	public static class MiRecordsConverter {
		
		private IDMapperStack gdb;
		private Graph graph;
		private Map<String, Integer> index;
		private Map<String, List<String>> edges;
		
		private String systemCodeIn = "Q";
		private String systemCodeOut = "L";
		
		private List<String> genesNotFound;
		private List<String> foundConnections;
		
		public MiRecordsConverter(Args pargs) throws Exception {
			if(init()) {
				log.info("conversion of MiRecords file started ...\n");
				ArgsParser.convertAndWrite(pargs, pargs, new GraphBuilder() {
					public Graph buildGraph(File in) throws Exception {
						return importMiRecords(in);
					}
				});
				
				log.info("conversion of MiRecords file finalized.\n");
			} else {
				log.severe("BridgeDb IDMapper could not be initialized.\n");
			}
		}
		
		private boolean init() {
			BioDataSource.init();
			try
			{
				Class.forName("org.bridgedb.rdb.IDMapperRdb");
			} catch (ClassNotFoundException ex) {
				return false;
			}
			File f = new File ("conversion/resources/Hs_Derby_20110601.bridge");
			if (f.exists()) { 
				try {
					gdb = new IDMapperStack();
					gdb.addIDMapper("idmapper-pgdb:" + f.getAbsolutePath());
					gdb.setTransitive(true);
				} catch (IDMapperException e) {
					return false;
				}
			} else {
				return false;
			}
			return true;
		}
		
		public Graph importMiRecords(File input) throws IOException {
			genesNotFound = new ArrayList<String>();
			foundConnections = new ArrayList<String>();
			
			BufferedReader br = new BufferedReader(new FileReader(input));
			String[] header = br.readLine().split("\t");
			index = new HashMap<String, Integer>();
			
			for(int i = 0; i < header.length; i++) index.put(header[i], i);
			
			graph = new Graph();
			setNetworkAttributes(input);
			
			//Load each line into memory
			String line = null;
			List<String[]> rows = new ArrayList<String[]>();
			while((line = br.readLine()) != null) {
				line = removeInvalidXMLCharacters(line);
				String [] str = line.split("\t", header.length);
				if(str[index.get("Target gene_species_scientific")].equals("Homo sapiens")) {
					rows.add(line.split("\t", header.length));
				}
			}
			
			edges = new HashMap<String, List<String>>();
			for(String[] row : rows) {
				
				String id = row[index.get("Target gene_Refseq_acc")];
				
				// map identifier to EntrezGene
				String newId = mapId(id);
				
				if(newId != null) {	
					// create gene node
					createGeneNode(newId, row);
					
					// create mirna node
					String mirna = createMiRNANode(row);
					
					// create edge
					addEdge(newId, mirna, row);
				} else {
					String str = id + "\t" + row[index.get("Target gene_name")];
					if(!genesNotFound.contains(str)) {
						genesNotFound.add(str);
					}	
				}
			}
			
			printResults(false);
			return graph;
		}
		
		private void setNetworkAttributes(File input) {
			graph.setTitle("MiRecords");
			graph.setAttribute(CommonAttributes.DATABASE.getName(), "MiRecords");
			graph.setAttribute(CommonAttributes.TYPE.getName(), "miRNA targets");
			graph.setAttribute(CommonAttributes.SOURCE_FILE.getName(), input.getName());
			graph.setAttribute(CommonAttributes.TARGET_ID_ATTRIBUTE.getName(), "EntrezGene");
			graph.setAttribute(CommonAttributes.TARGET_DATASOURCE.getName(), "EntrezGene");
			graph.setAttribute(CommonAttributes.SOURCE_ID_ATTRIBUTE.getName(), "miRNA");
			graph.setAttribute(CommonAttributes.SOURCE_DATASOURCE.getName(), "");
			graph.setAttribute(CommonAttributes.SOURCE_TYPE.getName(), "mirna");
			graph.setAttribute(CommonAttributes.TARGET_TYPE.getName(), "gene");
		}
		
		private void printResults(boolean printDetails) {
			
			System.out.println("no mapping for " + genesNotFound.size() + " genes.");
			System.out.println("found " + foundConnections.size()+ " miRNA-gene interactions.\n");
			
			if(printDetails) {
				System.out.println("------------------------\n");
				System.out.println("GENES NOT FOUND " + genesNotFound.size());
				for(String str : genesNotFound) {
					System.out.println(str);
				}
			
				System.out.println("FOUND CONNECTIONS " + foundConnections.size());
				for(String str : foundConnections) {
					System.out.println(str);
				}	
				
				System.out.println("\n------------------------");
				System.out.println("no mapping for " + genesNotFound.size() + " genes.");
				System.out.println("found " + foundConnections.size()+ " miRNA-gene interactions.\n");
			}
		}
		
		private void createGeneNode(String id, String [] r) {
			Node n = graph.addNode(id);
			n.setAttribute("EntrezGene", id);
			n.setAttribute("label", r[index.get("Target gene_name")]);
			n.setAttribute("name", r[index.get("Target gene_name")]);
			n.setAttribute("Organism", r[index.get("Target gene_species_scientific")]);
			n.setAttribute("Target_site_number", r[index.get("Target site_number")]);
		}
		
		private String createMiRNANode(String [] r) {
			String mirna = processMirna(r[index.get("miRNA_mature_ID")], r[index.get("miRNA_species")]);
			
			Node mirnaNode = graph.addNode(mirna);
			mirnaNode.setAttribute("miRNA", mirna);
			mirnaNode.setAttribute("label", mirna);
			mirnaNode.setAttribute("name", mirna);
			mirnaNode.setAttribute("Organism", r[index.get("miRNA_species")]);
			mirnaNode.setAttribute("miRNA_regulation", r[index.get("miRNA_regulation")]);
			
			return mirna;
		}
		
		private String mapId(String oldId) {
			if(oldId.contains(".")) {
				int pos = oldId.indexOf(".");
				oldId = oldId.substring(0, pos);
			}
			Xref srcRef = new Xref(oldId, DataSource.getBySystemCode(systemCodeIn));

			Set<Xref> result;
			try {
				result = gdb.mapID(srcRef, DataSource.getBySystemCode(systemCodeOut));
				if(!result.isEmpty()) {
					return result.toArray(new Xref[0])[0].getId();
				}
			} catch (IDMapperException e) {
				return null;
			}
			return null;
		}
		
		private Integer countEdges = 0;
		
		private void addEdge(String gene, String mirna, String [] r) {
			if(edges.containsKey(gene)) {
				if(!edges.get(gene).contains(mirna)) {
					Edge e = graph.addEdge("" + countEdges, graph.getNode(mirna),graph.getNode(gene));
					setEdgeAttributes(e, r);
					
					edges.get(gene).add(mirna);
					foundConnections.add(gene + "\t" + mirna);
					countEdges++;
				}
			} else {
				Edge e = graph.addEdge("" + countEdges, graph.getNode(mirna),graph.getNode(gene));
				setEdgeAttributes(e, r);
				
				List<String> list = new ArrayList<String>();
				list.add(mirna);
				foundConnections.add(gene + "\t" + mirna);
				edges.put(gene, list);
				countEdges++;
			}
		}
		
		private void setEdgeAttributes(Edge e, String [] r) {
			e.setAttribute("miRNA_regulation", r[index.get("miRNA_regulation")]);
			e.setAttribute("Reporter_target_gene_region", r[index.get("Reporter_target gene/region")]);
			e.setAttribute("Reporter_link_element", r[index.get("Reporter link element")]);
			e.setAttribute("Test_method_inter", r[index.get("Test_method_inter")]);
			e.setAttribute("Target_gene_mRNA_level", r[index.get("Target gene mRNA_level")]);
			e.setAttribute("Description", r[index.get("Original description")]);
			e.setAttribute("Mutation_target_region", r[index.get("Mutation_target region")]);
			e.setAttribute("Post_mutation_method", r[index.get("Post mutation_method")]);
			e.setAttribute("Original_description_mutation_region", r[index.get("Original description_mutation_region")]);
			e.setAttribute("Target_site_position", r[index.get("Target site_position")]);
			e.setAttribute("miRNA_regulation_site", r[index.get("miRNA_regulation_site")]);
			e.setAttribute("Reporter_target_site", r[index.get("Reporter_target site")]);
			e.setAttribute("Reporter_link_element", r[index.get("Reporter link element")]);
			e.setAttribute("Test_method_inter_site", r[index.get("Test_method_inter_site")]);
			e.setAttribute("Original_description_inter_site", r[index.get("Original description_inter_site")]);
			e.setAttribute("Mutation_target_site", r[index.get("Mutation_target site")]);
			e.setAttribute("Post_mutation_method_site", r[index.get("Post mutation_method_site")]);
			e.setAttribute("Original_description_mutation_site", r[index.get("Original description_mutation_site")]);
			e.setAttribute("Additional_note", r[index.get("Additional note")]);
			e.setAttribute("Pubmed_id", r[index.get("Pubmed_id")]);
		}
		
		private String processMirna(String mirna, String species) {
			mirna = mirna.replace("[", "");
			mirna = mirna.replace("]", "");
			
			if(mirna.startsWith("miR")) {
				//Add species code
				String code = SpeciesCodes.getCode(species);
				if(code != null) mirna = code + "-" + mirna;
			}
			return mirna;
		}
		
		 /**
	     * Removes all invalid Unicode characters that are not suitable to be used either
	     * in markup or text inside XML Documents.
	     *
	     * Based on these recommendations
	     * http://www.w3.org/TR/2000/REC-xml-20001006#NT-Char
	     * http://cse-mjmcl.cse.bris.ac.uk/blog/2007/02/14/1171465494443.html
	     *
	     * @param s The resultant String stripped of the offending characters!
	     * @return
	     */
	    public String removeInvalidXMLCharacters(String s) {
	        StringBuilder out = new StringBuilder();

	        int codePoint;
	        int i = 0;

	        while (i < s.length())
	        {
	            // This is the unicode code of the character.
	            codePoint = s.codePointAt(i);
	            if ((codePoint == 0x9) ||
	                    (codePoint == 0xA) ||
	                    (codePoint == 0xD) ||
	                    ((codePoint >= 0x20) && (codePoint <= 0xD7FF)) ||
	                    ((codePoint >= 0xE000) && (codePoint <= 0xFFFD)) ||
	                    ((codePoint >= 0x10000) && (codePoint <= 0x10FFFF)))
	            {
	                out.append(Character.toChars(codePoint));
	            }
	            i += Character.charCount(codePoint);
	        }
	        return out.toString();
	    }	
	}
    
	private interface Args extends AHelp, AFilesIn, AFilesOut {}
}
